/**
 * Table of Items
 * 
 * @author remo.lemma@lu.unisi.ch
 */

package ch.unisi.inf.projects.java.uom.gui.table;

import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;

import java.util.Set;
import java.util.HashSet;
import java.util.Comparator;
import java.util.regex.Pattern;
import java.util.List;
import java.util.ArrayList;

import org.jdesktop.swingx.*;
import org.jdesktop.swingx.decorator.*;
import javax.swing.table.TableCellRenderer;

import ch.unisi.inf.projects.java.uom.elements.*;
import ch.unisi.inf.projects.java.uom.*;
import ch.unisi.inf.projects.java.uom.gui.commands.Command;
import ch.unisi.inf.projects.java.uom.gui.commands.ItemDeleteCommand;
import ch.unisi.inf.projects.java.uom.gui.table.item.UOMFilterPipeline;
import ch.unisi.inf.projects.java.uom.gui.table.item.UOMItemColumnControlButton;
import ch.unisi.inf.projects.java.uom.gui.table.item.UOMItemJTableListener;
import ch.unisi.inf.projects.java.uom.gui.table.item.UOMItemTableModel;


import ch.unisi.inf.projects.java.uom.gui.table.item.UOMItemTableCellRenderer;
import ch.unisi.inf.projects.java.uom.gui.table.item.filter.BooleanFilter;
import ch.unisi.inf.projects.java.uom.gui.table.item.filter.NumberFilter;
import ch.unisi.inf.projects.java.uom.gui.table.itemclass.UOMItemClassJTableListener;
import ch.unisi.inf.projects.java.uom.gui.itemeditor.ItemEditorListener;
import ch.unisi.inf.projects.java.uom.exceptions.UOMException;
import ch.unisi.inf.projects.java.uom.gui.commands.CommandListener;


public class UOMItemJTable extends JXTable implements UOMItemClassJTableListener, DocumentListener, ItemEditorListener, CommandListener {

	/** We are not going to serialize this class atm */
    private static final long serialVersionUID = -1L;
    
    private static final short FORM_STRING = 0;
    private static final short FORM_BOOLEAN = 1;
    private static final short FORM_NUMBER = 2;
        
    private Set<ItemClass> itemClasses;
    
    private final List<UOMItemJTableListener> listeners;
    
    private boolean visible;
    
    private UOMFilterPipeline pipeline;
            
    private JPanel form;
    
    private int formState;
    
	public UOMItemJTable (final JPanel form)
	{
		super();
		this.setModel(new UOMItemTableModel(this));
		this.form = form;
		visible = true;
		pipeline = new UOMFilterPipeline(this);
		listeners = new ArrayList<UOMItemJTableListener>();
		itemClasses = new HashSet<ItemClass>();
		this.formState = FORM_STRING;
		this.setDefaultRenderer(String.class, new UOMItemTableCellRenderer());
		final TableCellRenderer doubleRenderer = this.getDefaultRenderer(Double.class);
		final TableCellRenderer newDoubleRenderer = new TableCellRenderer () { // doesn't work with the normal UOM Renderer
			public Component getTableCellRendererComponent(final JTable table, final Object value, final boolean isSelected, final boolean hasFocus, final int row, final int column) 
			{
		    	setToolTipText("<html><b>" + table.getColumnName(column) + "</b>: " + value + "</html>");
				return doubleRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
			}
		};
		this.setDefaultRenderer(Double.class, newDoubleRenderer);
		this.setColumnControlVisible(true);
		this.setColumnControl(new UOMItemColumnControlButton(this));
		
		final UOMItemTableModel model = (UOMItemTableModel) this.getModel();
		
		this.addMouseListener(new MouseAdapter () {
			public void mousePressed (final MouseEvent ev) { checkPopup(ev); } 
			public void mouseReleased (final MouseEvent ev) { checkPopup(ev); }
			private void checkPopup (final MouseEvent ev) { if (ev.isPopupTrigger()) showPopup(ev.getPoint()); }
		});
		ItemManager.instance().addItemManagerListener(new ItemManagerListener() {
			public void itemAdded (final Item i) { model.addRow(i); }
			public void focusOnItem (final Item i) { return; }
			public void itemClassAdded (final ItemClass c) { return; }
			public void elementDeleted (final Element e) { model.removeRow(e);  }
			public void elementModified (final Element e) { model.updateRow(e); }
			public void databaseOpened () { setEnabled(true); }
			public void databaseClosed () { model.clear(); model.setColumnCount(0); model.setRowCount(0); setEnabled(false); } });
		this.addKeyListener(new KeyAdapter() {
			public void keyPressed (KeyEvent ev) {
				if (ev.getKeyCode() == KeyEvent.VK_D && ev.getModifiers() == Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())
					new ItemDeleteCommand(new HashSet<Element>(getSelectedItems())).actionPerformed(null);
			}
		});
		this.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
			public void valueChanged (final ListSelectionEvent e) {
				final int[] rows = getSelectedRows();
				if (rows.length == 1 && visible)
					fireItemSelected(); 
			} });
	}
	
	public void createForm ()
	{
		final UOMItemTableModel model = (UOMItemTableModel) this.getModel();
		final int col = this.getSortedColumn().getModelIndex();
		form.removeAll();
		if (this.getColumnClass(col) == Boolean.class)
		{
			form.setLayout(new FlowLayout());
			final JComboBox comboBox = new JComboBox(new String[] { "All", "Item Present", "Item not Present" });
			comboBox.addItemListener(new ItemListener() {
				public void itemStateChanged (final ItemEvent ev)
				{
					applyFilter(comboBox.getSelectedItem());
				}});
			form.add(comboBox);
			formState = FORM_BOOLEAN;
		}
		else if ((Parser.instance().dbColumnTypeIsList(model.getRealColumnName(col)) ||
				this.getColumnClass(col) == Double.class)) {
			final boolean list = Parser.instance().dbColumnTypeIsList(model.getRealColumnName(col));
			form.setLayout(new FlowLayout());
			final JComboBox comboBox = new JComboBox(new String[] { "=", ">", "<", ">=", "<="  });
			form.add(comboBox);
			final JTextField textField = new JTextField();
			textField.setMinimumSize(new Dimension(200,26));
			textField.setPreferredSize(new Dimension(200,26));
			form.add(textField);
			if (list)
				form.add(new JLabel(" (number of elements in the list) "));
			final JLabel error = new JLabel();
			error.setForeground(Color.RED);
			form.add(error);
			comboBox.addItemListener(new ItemListener() {
				private boolean isValidInput() {
					final String text = textField.getText();
					if (text.equals("")) {
						error.setText("");
						return true;
					}
					try {
						if (list)
							Integer.parseInt(text);
						else
							Double.parseDouble(text);
						error.setText("");
						return true;
					}
					catch (final NumberFormatException nfe)
					{
						error.setText("Invalid value");
						return false;
					}
				}
				public void itemStateChanged (final ItemEvent ev)
				{
					if (isValidInput())
						applyFilter(textField.getText(), comboBox.getSelectedItem().toString(), list);
					
				}});
			textField.getDocument().addDocumentListener(new DocumentListener() {
				public void changedUpdate (final DocumentEvent e) { if (isValidInput()) applyFilter(e, comboBox.getSelectedItem().toString(), list); }
				public void insertUpdate (final DocumentEvent e) { if (isValidInput()) applyFilter(e, comboBox.getSelectedItem().toString(), list); }
				public void removeUpdate (final DocumentEvent e) { if (isValidInput()) applyFilter(e, comboBox.getSelectedItem().toString(), list); }
				private boolean isValidInput() {
					final String text = textField.getText();
					if (text.equals("")) {
						error.setText("");
						return true;
					}
					try {
						if (list)
							Integer.parseInt(text);
						else
							Double.parseDouble(text);
						error.setText("");
						return true;
					}
					catch (final NumberFormatException nfe)
					{
						error.setText("Invalid value");
						return false;
					}
				} });
			formState = FORM_NUMBER;
		}
		else {
			final SpringLayout layout = new SpringLayout();
			form.setLayout(layout);
	      	final JTextField filterText = new JTextField();
	       	filterText.getDocument().addDocumentListener(this);
	       	form.add(filterText);
	       	layout.putConstraint(SpringLayout.WEST, filterText, 5, SpringLayout.WEST, form);
	       	layout.putConstraint(SpringLayout.NORTH, filterText, 5, SpringLayout.NORTH, form);
	       	layout.putConstraint(SpringLayout.EAST, form, 5, SpringLayout.EAST, filterText);
	       	layout.putConstraint(SpringLayout.SOUTH, form, 5, SpringLayout.SOUTH, filterText);
	       	formState = FORM_STRING;
		}
       	form.revalidate();
       	form.repaint();
	}
	
	public void toggleSortOrder (final int col)
	{
		final Comparator<String> comparatorDoublesFromString = new Comparator<String>() {
			public int compare (final String d1, final String d2) {
				try {
					return Double.compare(Double.parseDouble(d1), Double.parseDouble(d2));
				}
				catch (final NumberFormatException nfe)
				{
					if (d2.equals(""))
						return 1;
					else if (d1.equals(""))
						return -1;
					else
						return 0;
				}
			}
		};
		if (this.getColumnClass(col) == Double.class)
			this.getColumnExt(col).setComparator(comparatorDoublesFromString);
		pipeline.flush();
		pipeline = new UOMFilterPipeline(this);
		this.getFilters().flush();
		this.setFilters(pipeline);
		super.toggleSortOrder(col);
		createForm();
	}
	
	public void itemClassesSelected (final UOMItemClassJTable table, final Set<ItemClass> itemClasses)
	{
		if (this.itemClasses.containsAll(itemClasses) && itemClasses.containsAll(this.itemClasses))
			return;
		this.itemClasses.clear();
		this.itemClasses = itemClasses;
		final UOMItemTableModel model = (UOMItemTableModel) this.getModel();
		final Set<String> commonFields = new HashSet<String>();
		final ItemManager manager = ItemManager.instance();
		for (final ItemClass itemClass : itemClasses) {
			final Set<String> fields = itemClass.getFields().keySet();
			if (commonFields.isEmpty())
				commonFields.addAll(fields);
			else
				commonFields.retainAll(fields);
		}
		try {
			model.clear();
			model.setColumnCount(0);
			model.setRowCount(0);
			for (final String field : ConstantArrays.ITEM_COMMON_FIELDS.value()) {
				if (!Constants.ITEM_PRIMARY_KEY.value().equals(field) && !ConstantArrays.ITEM_INVISIBLE_FIELDS.contains(field))
					model.addColumn(field);
			}
			model.addColumn(Constants.ITEM_CATEGORY_FIELD.value());
			model.addColumn(Constants.ITEM_HASPREVIEW_FIELD.value());
			for (final String field : commonFields) {
				if (!Constants.ITEM_PRIMARY_KEY.value().equals(field) && !ConstantArrays.ITEM_COMMON_FIELDS.contains(field) && !Parser.instance().dbColumnTypeIsList(field) && Parser.instance().dbGetVarType(field).equals(Constants.ITEM_CLASS_STRING.value()))
					model.addColumn(field);
			}
			for (final String field : commonFields) {
				if (!Constants.ITEM_PRIMARY_KEY.value().equals(field) && !ConstantArrays.ITEM_COMMON_FIELDS.contains(field) && !Parser.instance().dbColumnTypeIsList(field) && !Parser.instance().dbGetVarType(field).equals(Constants.ITEM_CLASS_STRING.value()))
					model.addColumn(field);
			}
			for (final String field : commonFields) {
				if (!Constants.ITEM_PRIMARY_KEY.value().equals(field) && !ConstantArrays.ITEM_COMMON_FIELDS.contains(field) && Parser.instance().dbColumnTypeIsList(field))
					model.addColumn(field);
			}
			model.fireTableStructureChanged();
			for (final ItemClass itemClass : itemClasses) {
				final Set<Item> items = manager.getItemsFromItemClass(itemClass);
				if (items.size() > 0 && this.getColumnExt(Constants.ITEM_PRIMARY_KEY.value()) == null) {
					model.addColumn(Constants.ITEM_PRIMARY_KEY.value().toUpperCase());
					this.getColumnExt(Constants.ITEM_PRIMARY_KEY.value()).setVisible(false);
				}
				
				for (final Item i : items)
					model.addRow(i);
			}
			this.formState = FORM_STRING;
			this.toggleSortOrder(0); // Sort
		}
		catch (final Exception ex)
		{
		}
	}
	
	public void tableHidden (final UOMItemClassJTable table) { return; }
	public void tableShown (final UOMItemClassJTable table) { return; }
	
	public void applyFilter (final Object value)
	{
		if (formState != FORM_BOOLEAN)
			return; // should not happen
		this.getSelectionModel().removeSelectionInterval(0, this.getRowCount() - 1);
		final UOMFilterPipeline pipe = new UOMFilterPipeline(this, new Filter[] { new BooleanFilter(value.toString(), this.getSortedColumn().getModelIndex()) });
		this.pipeline = pipe;
		this.setFilters(pipe);
	}
	
	public void applyFilter (final String content, final String operator, final boolean list)
	{
		if (formState != FORM_NUMBER)
			return;
		this.getSelectionModel().removeSelectionInterval(0, this.getRowCount() - 1);
		try {
			if (content.equals("")) {
				this.setFilters(new FilterPipeline(new Filter[] {}));
				return;
			}
			final Double value = Double.parseDouble(content);	
			final UOMFilterPipeline pipe = new UOMFilterPipeline(this,new Filter[] { new NumberFilter(operator, value, list, this.getSortedColumn().getModelIndex()) });
			this.pipeline = pipe;
			this.setFilters(pipe);
		}
		catch (final Exception ex)
		{
			return;
		}
	}
	
	public void applyFilter (final DocumentEvent e, final String operator, final boolean list)
	{
		if (formState != FORM_NUMBER)
			return;
		this.getSelectionModel().removeSelectionInterval(0, this.getRowCount() - 1);
		try {
			final String content = e.getDocument().getText(0, e.getDocument().getLength()).toUpperCase();
			applyFilter(content,operator,list);
		}
		catch (final Exception ex)
		{
			return;
		}
	}
	
	public void applyFilter (final DocumentEvent e)
	{
		if (formState != FORM_STRING)
			return;
		this.getSelectionModel().removeSelectionInterval(0, this.getRowCount() - 1);
		try {
			if (formState != FORM_STRING || e == null)
				return; // should not happen
			final String filter = e.getDocument().getText(0, e.getDocument().getLength()).toUpperCase();
			final UOMFilterPipeline pipe = new UOMFilterPipeline(this, new Filter[] { new PatternFilter("^" + filter + ".*",Pattern.CASE_INSENSITIVE, this.getSortedColumn().getModelIndex()) });
			this.pipeline = pipe;
			this.setFilters(pipe);
		}
		catch (final Exception ex)
		{
			return;
		}
	}
	
	public void changedUpdate (final DocumentEvent e) { applyFilter(e); }
	public void insertUpdate (final DocumentEvent e) { applyFilter(e); }
	public void removeUpdate (final DocumentEvent e) { applyFilter(e); }
	
	private Set<Item> getSelectedItems ()
	{
		final UOMItemTableModel model = (UOMItemTableModel) this.getModel();
		final int[] rows = this.getSelectedRows();
		final Set<Item> items = new HashSet<Item>(rows.length);
		for (int i = 0; i < rows.length; i++)
			items.add(model.getItem(rows[i]));
		return items;
	}
	
	private void showPopup (final Point p)
	{
		final JPopupMenu popup = new JPopupMenu();
		final JMenuItem delete = new JMenuItem();
		final int row = rowAtPoint(p);
		final ListSelectionModel model = this.getSelectionModel();
		final ItemDeleteCommand cmd = new ItemDeleteCommand(new HashSet<Element>(this.getSelectedItems()));
		
		if (!visible)
			return;
		else if (!model.isSelectedIndex(row))
			model.setSelectionInterval(row, row); 
		if (this.getSelectedRows().length ==  1)
			cmd.setNameAndDesc("Delete Item", "Deletes this item");
		else
			cmd.setNameAndDesc("Delete Selected Items", "Deletes the selected items");
		delete.setAction(cmd);
		delete.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_D, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
		popup.add(delete);
		popup.show(this, (int) p.getX(), (int) p.getY());
	}
	
	public void addUOMItemJTableListener (final UOMItemJTableListener listener)
	{
		listeners.add(listener);
	}
	
	public void removeUOMItemJTableListener (final UOMItemJTableListener listener)
	{
		listeners.remove(listener);
	}
	
	private void fireTableShow ()
	{
		visible = true;
		for (final UOMItemJTableListener li : listeners)
			li.tableShown(this);
	}
	
	private void fireTableHide ()
	{
		visible = false;
 		for (final UOMItemJTableListener li : listeners)
			li.tableHidden(this);
	}
	
	public void toShow()
	{
		fireTableShow();
	}
	
	public void toHide()
	{
		fireTableHide();
	}
	
	private void fireItemSelected ()
	{
		final UOMItemTableModel model = (UOMItemTableModel) this.getModel();
		for (final UOMItemJTableListener li : listeners)
			li.itemSelected(this, model.getItem(this.getSelectedRow()));
	}
	
	public boolean isVisible ()
	{
		return visible;
	}
	
	public List<String> getRealColumns (final Item item)
	{
		final List<String> columns = new ArrayList<String>();
		if (item == null)
			return columns;
		final ItemClass itemClass = item.getItemClass();
		final Set<String> fields = itemClass.getFields().keySet();

		for (final String field : ConstantArrays.ITEM_COMMON_FIELDS.value()) {
			if (!Constants.ITEM_PRIMARY_KEY.value().equals(field) && !ConstantArrays.ITEM_INVISIBLE_FIELDS.contains(field))
				columns.add(field);
		}
		columns.add(Constants.ITEM_CATEGORY_FIELD.value());
		columns.add(Constants.ITEM_HASPREVIEW_FIELD.value());
		for (final String field : fields) {
			if (!Constants.ITEM_PRIMARY_KEY.value().equals(field) && !ConstantArrays.ITEM_COMMON_FIELDS.contains(field))
				columns.add(field);
		}
		return columns;
	}
	
	public Item getItem (final int row)
	{
		final UOMItemTableModel model = (UOMItemTableModel) this.getModel();
		try {
			return model.getItem(row);
		}
		catch (final ArrayIndexOutOfBoundsException ex) {
			return null;
		}
		
	}
	
	public void itemChanged (final Item i) { 
		final UOMItemTableModel model = (UOMItemTableModel) this.getModel();
		final UOMItemColumnControlButton ccb = (UOMItemColumnControlButton) this.getColumnControl();
		ccb.hide();
		try {
			boolean ok = false;
			for (final ItemClass itemClass : itemClasses) {
				if (itemClass.toString().toUpperCase().equals(i.getItemClass().toString().toUpperCase())) {
					ok = true; break;
				}
			}
			if (!ok)
				return;
			if (i != null && !i.isNew() && model.findRow(i) == -1)
				model.addRow(i);
		}
		catch (final UOMException uome)
		{
			return;
		}
	}
	public void itemDeleted () { return; }
	public void itemSaved () { return; }
	
	
	public UOMFilterPipeline getPipeline ()
	{
		return this.pipeline;
	}

	public void ActionPerformed (final Command c) {
		this.getSelectionModel().removeSelectionInterval(0, this.getRowCount() - 1);
	}

}